package com.gxar.marketdelivery.shared.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @author nice
 * @version 1.0
 * @created 2023/7/21.
 */
public class AssemblerUtils2 {
    private static final Logger LOG = LoggerFactory.getLogger(AssemblerUtils2.class);

    private AssemblerUtils2() {
    }

    /**
     * 通过字段动态组装对象.
     * 如果没有Date和Long/String互转的字段，最好使用spring的
     * {@link org.springframework.beans.BeanUtils#copyProperties(Object, Object, String...)}方法，性能会好一些。
     * <p/>
     * 与老AssemblerUtils.assemble()方法相比：
     * 1 性能比老方法提升7倍左右
     * 2 老方法通过get set 属性的方式进行装配，这里使用字段直接赋值的方式
     * 3 老方法支持名为assembleTrigger的特定方法，这里不支持
     *
     * @param fromObj       源对象
     * @param toObj         目标对象
     * @param excludeFields 不需要组装的属性
     * @param <T>
     * @return toObj
     */
    public static <T> T assemble(Object fromObj, T toObj, String... excludeFields) {
        return assemble(fromObj, toObj, false, excludeFields);
    }

    public static <T> T assembleNotNull(Object fromObj, T toObj, String... excludeFields) {
        return assemble(fromObj, toObj, true, excludeFields);
    }

    private static <T> T assemble(Object fromObj, T toObj, boolean onlyNotNull, String... excludeFields) {
        if (fromObj == null || toObj == null) {
            return toObj;
        }

        Map<String, Field> fromFieldMap = getInstantFields(fromObj.getClass());
        for (String excludeField : excludeFields) {
            fromFieldMap.remove(excludeField);
        }

        try {
            Collection<Field> toFields = getInstantFields(toObj.getClass()).values();
            for (Field toField : toFields) {
                Field fromField = fromFieldMap.get(toField.getName());
                if (fromField == null) {
                    continue;
                }

                Object fromValue = getFieldValue(fromField, fromObj);
                if (onlyNotNull && fromValue == null) {
                    continue;
                }

                if (fromField.getType().equals(toField.getType())) {
                    setFieldValue(toField, toObj, fromValue);
                } else if (Date.class.isAssignableFrom(fromField.getType()) || Date.class.equals(toField.getType())) {
                    if (fromValue != null) {
                        setFieldValue(toField, toObj, DateTimeUtils.convertDate(fromValue, toField.getType()));
                    }
                }
            }

        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        }
        return toObj;
    }

    /**
     * 查找未匹配的字段(主要调试用)
     *
     * @param left
     * @param right
     * @return
     */
    public static Map<String, List<String>> findUnmatchedFields(Class left, Class right) {
        Map<String, List<String>> result = new HashMap<>();
        List<String> leftUnmatch = new ArrayList<>();
        List<String> rightUnmatch = new ArrayList<>();

        Map<String, Field> fromFieldMap = getInstantFields(left);
        Map<String, Field> toFieldMap = getInstantFields(right);

        for (Field toField : toFieldMap.values()) {
            Field fromField = fromFieldMap.get(toField.getName());
            if (fromField == null) {
                rightUnmatch.add(toField.getName());
                continue;
            }

            // use same logic as method assemble()
            if (fromField.getType().equals(toField.getType())) {

            } else if (Date.class.isAssignableFrom(fromField.getType()) || Date.class.equals(toField.getType())) {

            } else {
                rightUnmatch.add(toField.getName());
            }
        }

        for (Field fromField : fromFieldMap.values()) {
            Field toField = toFieldMap.get(fromField.getName());
            if (toField == null) {
                leftUnmatch.add(fromField.getName());
                continue;
            }

            // use same logic as method assemble()
            if (toField.getType().equals(fromField.getType())) {

            } else if (Date.class.isAssignableFrom(toField.getType()) || Date.class.equals(fromField.getType())) {

            } else {
                leftUnmatch.add(fromField.getName());
            }
        }

        if (CollectionUtils.isNotEmpty(leftUnmatch)) {
            result.put("left", leftUnmatch);
        }
        if (CollectionUtils.isNotEmpty(rightUnmatch)) {
            result.put("right", rightUnmatch);
        }
        return result;
    }

    /**
     * 根据目标类型组装对象
     *
     * @param fromObj
     * @param targetClz
     * @param excludeFields
     * @param <T>
     * @return
     */
    public static <T> T assemble(Object fromObj, Class<T> targetClz, String... excludeFields) {
        T toObject = null;
        if (fromObj == null) {
            return toObject;
        }
        try {
            toObject = targetClz.newInstance();
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            return toObject;
        }
        return assemble(fromObj, toObject, excludeFields);
    }

    /**
     * 从源List装配一个符合目标class类型的List.
     *
     * @param fromList      源list
     * @param toClass       目标class
     * @param excludeFields 不装配的属性名称
     * @return
     * @author zhaolei
     * @created 2011-4-28
     */
    public static <T> List<T> assembleList2NewList(List<?> fromList, Class<T> toClass, String... excludeFields) {
        if (CollectionUtils.isEmpty(fromList)) {
            return Collections.emptyList();
        }
        List<T> toList = new ArrayList<>(fromList.size());
        try {
            for (Iterator<?> iterator = fromList.iterator(); iterator.hasNext(); ) {
                Object fromObject = iterator.next();
                T toObject;
                if (excludeFields != null && excludeFields.length > 0) {
                    toObject = assemble(fromObject, toClass, excludeFields);
                } else {
                    toObject = assemble(fromObject, toClass);
                }
                toList.add(toObject);
            }
        } catch (Exception e) {
            LOG.error(e.toString(), e);
        }
        return toList;
    }

    /**
     * 从源List装配一个符合目标class类型的List.
     *
     * @param fromList      源List
     * @param toList        目标List
     * @param excludeFields 不需要装配的属性
     * @return
     * @author zhaolei
     * @created 2011-4-28
     */
    public static <T> List<T> assembleList2List(List<?> fromList, List<T> toList, Class<T> toClass,
            String... excludeFields) {
        try {
            for (int i = 0; i < fromList.size(); i++) {
                Object fromObject = fromList.get(i);
                T toObject = i >= toList.size() ? toClass.newInstance() : toList.get(i);
                if (excludeFields.length > 0)
                    toObject = assemble(fromObject, toObject, excludeFields);
                else
                    toObject = assemble(fromObject, toObject);
                toList.add(toObject);
            }
        } catch (Exception e) {
            LOG.error(e.toString(), e);
        }
        return toList;
    }

    private static Map<String, Field> getInstantFields(Class cls) {

        Map<String, Field> fieldMap = new HashMap<String, Field>();
        while (cls != null) {
            Field[] fields = cls.getDeclaredFields();
            for (Field field : fields) {
                if (!Modifier.isStatic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers())) {
                    fieldMap.putIfAbsent(field.getName(), field);
                }
            }
            cls = cls.getSuperclass();
        }

        return fieldMap;
    }

    private static Object getFieldValue(Field field, Object obj) throws IllegalAccessException {
        boolean originalAccessible = field.isAccessible();
        field.setAccessible(true);
        Object val = field.get(obj);
        field.setAccessible(originalAccessible);
        return val;
    }

    private static void setFieldValue(Field field, Object obj, Object val) throws IllegalAccessException {
        boolean originalAccessible = field.isAccessible();
        field.setAccessible(true);
        field.set(obj, val);
        field.setAccessible(originalAccessible);
    }
}
